/***************************************************************************************
  *Copyright(C),2010-2016,Sumscope
  *FileName   : TableFile.cpp
  *Author     : scofined.q
  *Version    : 1.0
  *Date       : 2016/06/02
  *Desc       : //用于说明此程序文件完成的主要功能
  *Relation   :
  *Other      : //其他内容说明
  *Function   : //主要函数列表，每条记录应包含函数名及功能简要说明
  *History    : //修改历史纪录列表，每条修改记录应包含修改日期，修改者以及修改内容简介
  ***************************************************************************************/
#include "TableFile.h"
#include <core/system/memory.h>

#ifdef WIN32
#include <windows.h>
#else
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#endif

#ifdef WIN32
#define BASSERT assert
#else
#define BASSERT //
#endif

namespace qb{
	namespace ht{
#define ASSERT_READ_RID(RID)	BASSERT(RID < m_record_count);
#define ASSERT_WRITE_RID(RID)	BASSERT(RID <= m_record_count);
#define ASSERT_ENCRYPTSIZE(size)
#define ASSERT_RECORD_SIZE(size) BASSERT( (m_encryptor!=0 && ROUND_ALIGN(size,16)==m_record_length)|| (m_encryptor==0 && ROUND4(size) == m_record_length));

        bool TableFileView::Acquire(void* mapping, int start, int size)
		{
            BASSERT(mapping != NULL);
			m_start = start;
			m_size = size;
#ifdef WIN32
			if (m_last)
                m_view = (char*)MapViewOfFileEx(mapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, start, size, m_last);
			if (m_view == 0)
                m_view = (char*)MapViewOfFileEx(mapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, start, size, 0);
#else
            m_view = (char*)((char*)mapping+start);
#endif
			return m_view != NULL;
		}

		void TableFileView::Release()
		{
			m_last = m_view;
			if (m_view != NULL)
			{
#ifdef WIN32
                UnmapViewOfFile(m_view);
#endif
			}
			m_view = NULL;
		}

		void TableFileView::Write(int offset, const char* record, int size)
		{
            BASSERT(offset >= m_start && offset + size <= m_start + m_size);
            BASSERT(m_view != 0);
            BASSERT(record != 0);
			memcpy(m_view + (offset - m_start), record, size);
		}

		void TableFileView::Read(int offset, char* record, int size) const
		{
            BASSERT(offset >= m_start && offset + size < m_start + m_size);
            BASSERT(m_view != 0);
            BASSERT(record != 0);
			memcpy(record, m_view + (offset - m_start), size);
		}

		//////////////////////////////////////////////////////////////////////////
		TableFile::TableFile(int recordSize, int headSize, const char* tag, int viewsize,int increase) :
			m_initialized(false),
            m_init_pos(0), m_max_size(0),
			m_record_length(recordSize),
			m_max_viewid(0),
			m_remap_count(0),
			m_head_size(headSize),
			m_increase_size(increase*MEGA_BYTES),
			m_head_round_size(0),
			m_delete_file(true),
			m_record_count(0),
			m_head_reset(true),
			m_first_recordid(-1),
			m_encryptor(0),
            m_flag_update_count(false)
		{
#ifdef WIN32
            m_file = NULL;
#else
            m_file = -1;
#endif
            m_mapping = NULL;

            m_sys_pagesize = qb::base::getMemPageSize();
            BASSERT(m_sys_pagesize > 0);

			m_view_size = ROUND_ALIGN(MEGA_BYTES*viewsize, MEGA_BYTES);
			m_increase_size = ROUND_ALIGN(increase*MEGA_BYTES, MEGA_BYTES);
			m_increase_size = m_increase_size < m_view_size ? m_view_size : m_increase_size;
            BASSERT(m_view_size >= m_sys_pagesize * 2);

			// recordSize为单条记录的实际大小,m_record_length为其对4字节对齐后的大小,如果加密,则16字节对齐
            BASSERT(headSize >= 0);
			m_record_length = ROUND4(recordSize);
			if (tag)
				m_record_length = m_record_length % 16 == 0 ? (m_record_length + 16) : ((m_record_length/16+1)* 16);

			m_head_round_size = ROUND_ALIGN(m_head_size, m_record_length);
			m_head_view_size = ROUND_ALIGN(m_head_round_size, m_sys_pagesize);
			m_head_record_num = m_head_round_size / m_record_length;


			// 以此为分界点,小于此id的,交给m_head_view处理,大于此值的,交给m_view处理
			m_first_recordid = m_head_view_size/ m_record_length - m_head_record_num;
            BASSERT(m_record_length <= m_sys_pagesize);

			m_pod_size = m_record_length + 16; m_pod_size = ROUND_ALIGN(m_pod_size, 16);
			m_pod = new char[m_pod_size];
			memset(m_pod, 0, m_pod_size);
			SetTag(tag);
		}
		TableFile::~TableFile()
		{
#ifdef WIN32
			if (m_mapping != NULL)
			{
                m_fview.Release();
				m_head_view.Release();

				CloseHandle(m_mapping);
			}

            if (m_file != NULL)
            {
                if (m_delete_file)
                {
                    SetFilePointer(m_file, 0, 0, FILE_BEGIN);
                    SetEndOfFile(m_file);
                }
                CloseHandle(m_file);
            }

            if (m_delete_file && !m_fname.empty())
            {
                DeleteFileA(m_fname.c_str());
            }
#else
            if (m_mapping != NULL)
            {
                m_fview.Release();
                m_head_view.Release();

                munmap(m_mapping, m_max_size);
                m_mapping = NULL;
            }

            if (m_file != -1)
            {
                if (m_delete_file)
                {
                    ftruncate(m_file, 0);
                }
                close(m_file);
                m_file = -1;
            }

            if (m_delete_file && !m_fname.empty())
            {
                remove(m_fname.c_str());
            }
#endif

			if (m_encryptor)delete m_encryptor;
			if (m_pod)delete[] m_pod;
		}
		void TableFile::ReleaseView()
		{
            int memsize = m_fview.GetView() ? m_fview.GetViewSize() : 0;
			memsize += m_head_view.GetView() ? m_head_view.GetViewSize() : 0;
            m_fview.Release();
			m_head_view.Release();
			m_head_reset = true;
		}
		void TableFile::SetTag(const char* tag)
		{
			int len = tag?strlen(tag):0;
			if (len <= 0)
				return;
			if (m_encryptor == 0)
				m_encryptor = new BlockAes(tag);
			else
				m_encryptor->SetTag(tag);
			if (m_pod == 0)
				m_pod = new char[m_pod_size];
			memset(m_pod, 0, m_pod_size);
		}
		void TableFile::PrepareHead()
		{
			int ncount = 0;
			int vstart = m_head_view.GetStart(), vsize = m_head_view.GetViewSize();
			if (m_head_reset)
			{
				m_head_view.Release();
				m_head_view.Acquire(m_mapping, 0, m_head_view_size);
				m_head_reset = false;
				ncount++;
			}
			int ntry = 10;
			while (ntry > 0 && m_head_view.GetView() == NULL)
			{
				ntry--;
				m_head_view.Release();
                m_head_view.Acquire(m_mapping, 0, m_head_view_size);
				m_head_reset = false;
				ncount++;
			}
#ifdef DEBUG_CHECK
			if (ncount > 0)
			{
				BLOGINFO("[DICT][TABLEFILE]%32s:%2d prepare head %8d/%8d ==> %8d/%8d  viewsize:%8d",
					ss::base::StringUtil::GetShortFileName(m_fname.c_str(), 1),ncount,
					vstart / m_view_size, vsize / m_view_size,
					m_view.GetStart() / m_view_size, m_view.GetViewSize() / m_view_size,
					m_view_size
					);
			}
#endif
            BASSERT(m_head_view.GetView() != 0);
		}



		bool TableFile::ReadHead(char*buf, int size, int offset)
		{
			// 从映射文件开头读取size个字节到buf中
			// 如果已经同步过了,那么内存中的数据m_head_buf与映射文件中的相同
            BASSERT(m_initialized);
            BASSERT(m_head_size > 0 && m_head_record_num > 0);
            BASSERT(offset >= 0 && size > 0 && offset+size <= m_head_size && buf);
			PrepareHead();
			if (m_head_view.GetView() == NULL)
				return false;
			m_head_view.Read(offset, buf, size);
			return true;
		}
		bool TableFile::WriteHead(const char*buf, int size,int count)
		{
			// 将外部更新的文件头更新到映射文件中
			// 首先尝试同步
			// 其次将数据拷贝到m_head_buf中
			// 再次将数据写入到映射文件中
			// m_head_view的固定大小:比头部大,是m_sys_pagesize的整数倍
			// m_head_view能管辖的rid范围
			PrepareHead();
			m_record_count = count;
			m_head_view.Write(0,buf, size);

#ifdef WIN32
			FlushViewOfFile(m_head_view.GetView(), m_head_view.GetViewSize());
#else
            msync((void*)m_head_view.GetView(), m_head_view.GetViewSize(), MS_SYNC);
#endif
			return true;
		}
		
		bool TableFile::Initialize(const char* fname, bool reset)
		{
			if (m_initialized)
				return true;
			/*
			FILE_FLAG_NO_BUFFERING 会使得性能降低
			*/
			m_fname = fname;

#ifdef WIN32
            unsigned long fileattr = FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_WRITE_THROUGH;
            if (m_delete_file)
                fileattr |= FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE;
            m_file = CreateFileA(fname, GENERIC_WRITE | GENERIC_READ,
                0/*FILE_SHARE_READ*/, NULL, OPEN_ALWAYS, fileattr, NULL);
            if (m_file == INVALID_HANDLE_VALUE || m_file == NULL)
            {
                return false;
            }
            if (reset)
            {
                SetFilePointer(m_file, 0, 0, FILE_BEGIN);
                SetEndOfFile(m_file);
            }
            unsigned long file_size_high = 0;
            m_init_pos = ::GetFileSize(m_file, &file_size_high);
#else
            m_file = open(fname, O_RDWR);
            if (m_file == -1)
            {
                return false;
            }
            if (reset)
            {
                ftruncate(m_file, 0);
            }

            struct stat fileStat;
            stat(fname, &fileStat);
            m_init_pos = fileStat.st_size;
#endif

			PrepareView(0);
			PrepareHead();
			
			m_initialized = true;
			if (!ReadHead((char*)&m_record_count, sizeof(int)))
			{
				m_initialized = false;
				return false;
			}

			m_initialized = true;
			return true;
		}

		ViewInfo TableFile::GetViewInfo() const
		{
			ViewInfo vi;
			vi.viewid = 0;// m_view.GetViewId();
			vi.maxviewid = m_max_viewid;
			vi.viewsize = m_view_size;
			vi.recordsize = m_record_length;

			int offset = vi.viewid*vi.viewsize;
			vi.rid_start = offset / m_record_length;
			if (offset % m_record_length != 0)
				vi.rid_start++;
			vi.rid_end = (offset + vi.viewsize) / m_record_length;
			return vi;
		}
		int TableFile::CalculateView(int offset)
		{
			// offset为映射文件中的偏移量
			// 此函数用于计算最佳的映射视图的开始位置的偏移量
			// 计算位置为offset,长度为m_record_length的内容对应的映射视图的起始位置
			// 尽力按m_view_size对齐
			// 当这条记录落在两个view上时(首落在前一个,尾巴落在后一个)
			// 选取居中的view(此时不按m_view_size边界对齐,但也必须按m_sys_pagesize对齐)
			// 要求一条记录不能超过m_sys_pagesize大小

			// 计算当前m_view所需要的映射区间,确保包含 offset+m_record_length,且以
			// m_view_size或者m_sys_pagesize边界对齐
			// 优先以m_view_size边界对齐
			int end = offset + m_record_length;
			int maxend = GetMaxViewEnd();
            BASSERT(offset >= 0 && end <= maxend);
			int start = (offset / m_view_size)*m_view_size;
			if (start + m_view_size < end)
			{
				// 此条记录跨越原定的两个view的边界,此时,映射区间应该在[0,maxend]之内
				start = (offset / m_sys_pagesize)*m_sys_pagesize;
				if (start + m_view_size > maxend)
				{
					start = maxend - m_view_size;
				}
			}

            BASSERT(start >= 0 && start + m_view_size <= maxend && offset >= start);
			return start;
		}
		const char* TableFile::ReadDirectImpl(int rid)
		{
			// rid是外界检测过了的合法的记录索引值
			if (PrepareView(rid))
			{
				int offset = rid*m_record_length;
                return m_fview.GetView() + (offset - m_fview.GetStart());
			}
			return 0;
		}
		bool TableFile::PrepareView(RecordId rid)
		{
            BASSERT(rid >= 0);
            int offset = rid*m_record_length;
            if (m_fview.GetView()&&m_fview.Contains(offset, m_record_length))
				return true;

			// 逐步增加映射的文件尺寸,通常是用于逐个写入记录的时候
            int vstart=m_fview.GetStart(), vsize = m_fview.GetViewSize();
            m_fview.Release();
			int end = offset + m_record_length;
			while (end>m_max_size)
			{
				m_head_reset = true;
				m_head_view.Release();
#ifdef WIN32
				if (m_mapping)
					CloseHandle(m_mapping);
				m_mapping = NULL;
#else
                if (m_mapping)
                {
                    munmap(m_mapping, m_max_size);
                    m_mapping = NULL;
                }
#endif

				if (!PrepareMapping(m_max_size + m_increase_size))
				{
                    BASSERT(0);
					return false;
				}
			}
			PrepareHead();
            BASSERT(m_max_size%m_view_size == 0);

			m_max_viewid = m_max_size / m_view_size-1;
			int start = CalculateView(offset);

            if (!m_fview.Acquire(m_mapping, start, m_view_size))
			{
                BASSERT(0);
				return false;
			}

#ifdef DEBUG_CHECK
			BLOGINFO("[DICT][TABLEFILE]%32s prepare view %8d/%8d ==> %8d/%8d  viewsize:%8d",
				ss::base::StringUtil::GetShortFileName(m_fname.c_str(),1),
				vstart / m_view_size, vsize / m_view_size,
				m_view.GetStart() / m_view_size, m_view.GetViewSize() / m_view_size,
				m_view_size
				);
#endif
			return true;
		}
		bool TableFile::ReadImpl(RecordId rid, char* record, int size)
		{
            BASSERT(size <= m_record_length && rid>=0 && record);
			const char* data = ReadDirectImpl(rid);
			memcpy(record, data, size);
			return true;
		}
		bool TableFile::WriteImpl(RecordId rid, const char* record, int size)
		{
            BASSERT(rid >= 0 && record);
            ASSERT_RECORD_SIZE(size);
			if (PrepareView(rid))
			{
				const char* data = m_encryptor ? Encrypt(record, size) : record;
                m_fview.Write(rid*m_record_length, data, size);
				return true;
			}
			return false;
		}
		bool TableFile::PrepareMapping(int maxSize)
		{
            BASSERT(maxSize%m_increase_size == 0);
			m_max_size = maxSize;

#ifdef WIN32
            BASSERT(m_mapping == NULL);
            BASSERT(m_file != NULL);
            unsigned long flProtect = PAGE_READWRITE | SEC_COMMIT;
			m_mapping = CreateFileMapping(m_file, NULL, flProtect, 0, m_max_size, NULL);
			if (m_mapping == NULL)
			{
				return false;
			}
#else
            BASSERT(m_mapping == NULL);
            BASSERT(m_file != -1);
			if (m_init_pos < m_max_size)
			{
				ftruncate(m_file, m_max_size);
			}
            m_mapping = mmap(0, m_max_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_file, 0);
#endif

			return true;
		}
		int TableFile::GetMemory()const
		{
			int bytes = sizeof(*this);
            if (m_fview.GetView())
                bytes += m_fview.GetViewSize();
			if (m_head_view.GetView())
				bytes += m_head_view.GetViewSize();
			return bytes;
		}
		bool TableFile::Read(RecordId rid, char* record, int size)
		{
            ASSERT_RECORD_SIZE(size);
			const char* data = ReadDirect(rid);
			memcpy(record, data, size);
			return true;
		}
		const char* TableFile::ReadDirect(int rid)
		{
            ASSERT_READ_RID(rid);
			const char* record = 0;
			if (rid >= m_record_count || rid<0)
				return record;
			if (rid >= m_first_recordid)
				record= ReadDirectImpl(rid + m_head_record_num);
			else
			{
				PrepareHead();
				int offset = (rid + m_head_record_num)*m_record_length;
				record = m_head_view.GetView() + offset;
			}
			int len = m_record_length;
			return m_encryptor ? Decrypt(record, len) : record;
		}
		bool TableFile::SetRecordCount(RecordId rid)
		{
			if (rid == m_record_count)
			{
				m_record_count = rid + 1;
				return true;
			}
			return false;
		}
		bool TableFile::Write(RecordId rid, const char* record, int size)
		{
			/*
				有三种情况:
				rid>=m_first_recordid,这些记录由m_view来管理
				rid==m_split_recordid,由m_head_view和m_view共同处理
				rid<m_split_recordid,由m_head_view处理

				m_head_view包括写入文件头,和<=m_split_recordid的记录

				之所以如此,是因为:
					更新记录的时候,很可能是追加记录,那么记录个数就必须实时写入,否则可能导致下次
					启动的时候,不能获取记录个数
					而实时写入记录,如果使用m_view,那么会导致映射视图来回变换,极大影响性能

				这里使用两个映射窗口,m_head_view专门用于处理文件头附近,m_view专门用于处理文件中间

					还有一种办法:每次启动初始化的时候,扫描文件大小,自行纠正记录个数
						不过,也不一定起作用.可能末尾的都是无效记录.
			*/
            ASSERT_WRITE_RID(rid);
            ASSERT_RECORD_SIZE(size);
			bool suc = true;
			int ncount = m_record_count;
			SetRecordCount(rid);
			if (rid >= m_first_recordid)
				suc=WriteImpl(rid + m_head_record_num, record, size);
			else
			{
				int offset = (rid + m_head_record_num)*m_record_length;
				PrepareHead();
				const char* data = m_encryptor ? Encrypt(record, size) : record;
				m_head_view.Write(offset, data, size);
			}

			// 记录个数增加了,需要调整文件头中的记录个数字段
			if (m_flag_update_count && m_head_size>0 && ncount != m_record_count)
			{
				// 更新记录个数,强行约定最开始4个字节表示记录个数
				// 见TableHead定义,由静态宏CHECK_FIELD_POS来保证
				WriteHead((const char*)&m_record_count, sizeof(int), m_record_count);
			}
			return suc;
		}

		const char* TableFile::Encrypt(const char* record, int& len)
		{
			/*
			record是原始的没有加密的数据,len是其可能最大长度
			考虑问题简单化,假设支持加密的记录的长度必须是8的整数倍
			*/
            BASSERT(record && m_pod && m_encryptor && len > 0);
            len = m_encryptor->Encrypt(record, len, m_pod, m_pod_size);
            BASSERT(len % 16 == 0);

			return m_pod;
		}

		const char* TableFile::Decrypt(const char* record, int& len)
		{
            BASSERT(record && m_pod && m_encryptor && len % 16 == 0);
			len = m_encryptor->Decrypt(record, len, m_pod, m_pod_size);
			return m_pod;
		}

	}//namespace ht
}//namespace ss
